﻿//
// (C) Copyright 2009 Irantha Suwandarathna (iranthas@hotmail.com)
// All rights reserved.
//

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EffiProz.Core.Lib;
using EffiProz.Core.DataTypes;
using EffiProz.Core.Indexes;
using EffiProz.Core.Errors;
using EffiProz.Core.Persist;

namespace EffiProz.Core
{
    public class FunctionSQL : Expression
    {
        private const int FUNC_POSITION_CHAR = 1;     // numeric
        private const int FUNC_POSITION_BINARY = 2;
        private const int FUNC_OCCURENCES_REGEX = 3;
        private const int FUNC_POSITION_REGEX = 4;
        protected const int FUNC_EXTRACT = 5;
        protected const int FUNC_BIT_LENGTH = 6;
        protected const int FUNC_CHAR_LENGTH = 7;
        protected const int FUNC_OCTET_LENGTH = 8;
        private const int FUNC_CARDINALITY = 9;
        private const int FUNC_ABS = 10;
        private const int FUNC_MOD = 11;
        protected const int FUNC_LN = 12;
        private const int FUNC_EXP = 13;
        private const int FUNC_POWER = 14;
        private const int FUNC_SQRT = 15;
        private const int FUNC_FLOOR = 16;
        private const int FUNC_CEILING = 17;
        private const int FUNC_WIDTH_BUCKET = 20;
        protected const int FUNC_SUBSTRING_CHAR = 21;    // string
        private const int FUNC_SUBSTRING_REG_EXPR = 22;
        private const int FUNC_SUBSTRING_REGEX = 23;
        protected const int FUNC_FOLD_LOWER = 24;
        protected const int FUNC_FOLD_UPPER = 25;
        private const int FUNC_TRANSCODING = 26;
        private const int FUNC_TRANSLITERATION = 27;
        private const int FUNC_REGEX_TRANSLITERATION = 28;
        protected const int FUNC_TRIM_CHAR = 29;
        public const int FUNC_OVERLAY_CHAR = 30;
        private const int FUNC_CHAR_NORMALIZE = 31;
        private const int FUNC_SUBSTRING_BINARY = 32;
        private const int FUNC_TRIM_BINARY = 33;
        private const int FUNC_OVERLAY_BINARY = 40;
        protected const int FUNC_CURRENT_DATE = 41;    // datetime
        protected const int FUNC_CURRENT_TIME = 42;
        protected const int FUNC_CURRENT_TIMESTAMP = 43;
        protected const int FUNC_LOCALTIME = 44;
        protected const int FUNC_LOCALTIMESTAMP = 50;
        private const int FUNC_CURRENT_CATALOG = 51;    // general
        private const int FUNC_CURRENT_DEFAULT_TRANSFORM_GROUP = 52;
        private const int FUNC_CURRENT_PATH = 53;
        private const int FUNC_CURRENT_ROLE = 54;
        private const int FUNC_CURRENT_SCHEMA = 55;
        private const int FUNC_CURRENT_TRANSFORM_GROUP_FOR_TYPE = 56;
        private const int FUNC_CURRENT_USER = 57;
        private const int FUNC_SESSION_USER = 58;
        private const int FUNC_SYSTEM_USER = 59;
        protected const int FUNC_USER = 60;
        private const int FUNC_VALUE = 61;

        //
        public static short[] noParamList = new short[] { };
        public static short[] emptyParamList = new short[] {
        Tokens.OPENBRACKET, Tokens.CLOSEBRACKET
    };
        public static short[] optionalNoParamList = new short[] {
        Tokens.X_OPTION, 2, Tokens.OPENBRACKET, Tokens.CLOSEBRACKET
    };
       public static short[] singleParamList = new short[] {
        Tokens.OPENBRACKET, Tokens.QUESTION, Tokens.CLOSEBRACKET
    };
       public static short[] optionalIntegerParamList = new short[] {
        Tokens.X_OPTION, 3, Tokens.OPENBRACKET, Tokens.X_POS_INTEGER,
        Tokens.CLOSEBRACKET
    };
       public static short[] doubleParamList = new short[] {
        Tokens.OPENBRACKET, Tokens.QUESTION, Tokens.COMMA, Tokens.QUESTION,
        Tokens.CLOSEBRACKET
    };
       public static short[] tripleParamList = new short[] {
        Tokens.OPENBRACKET, Tokens.QUESTION, Tokens.COMMA, Tokens.QUESTION,
        Tokens.COMMA, Tokens.QUESTION, Tokens.CLOSEBRACKET
    };
       public static short[] quadParamList = new short[] {
        Tokens.OPENBRACKET, Tokens.QUESTION, Tokens.COMMA, Tokens.QUESTION,
        Tokens.COMMA, Tokens.QUESTION, Tokens.COMMA, Tokens.QUESTION,
        Tokens.CLOSEBRACKET
    };

        //
        public static IntValueHashMap valueFuncMap = new IntValueHashMap();
        public static IntValueHashMap regularFuncMap = new IntValueHashMap();

        static FunctionSQL()
        {
            regularFuncMap.put(Tokens.T_POSITION, FUNC_POSITION_CHAR);
            /*
            regularFuncMap.put(Token.T_OCCURENCES_REGEX, FUNC_OCCURENCES_REGEX);
            */
            regularFuncMap.put(Tokens.T_POSITION_REGEX, FUNC_POSITION_REGEX);
            regularFuncMap.put(Tokens.T_EXTRACT, FUNC_EXTRACT);
            regularFuncMap.put(Tokens.T_BIT_LENGTH, FUNC_BIT_LENGTH);
            regularFuncMap.put(Tokens.T_CHAR_LENGTH, FUNC_CHAR_LENGTH);
            regularFuncMap.put(Tokens.T_CHARACTER_LENGTH, FUNC_CHAR_LENGTH);
            regularFuncMap.put(Tokens.T_OCTET_LENGTH, FUNC_OCTET_LENGTH);
            /*
            regularFuncMap.put(Token.T_CARDINALITY, FUNC_CARDINALITY);
            */
            regularFuncMap.put(Tokens.T_ABS, FUNC_ABS);
            regularFuncMap.put(Tokens.T_MOD, FUNC_MOD);
            regularFuncMap.put(Tokens.T_LN, FUNC_LN);
            regularFuncMap.put(Tokens.T_EXP, FUNC_EXP);
            regularFuncMap.put(Tokens.T_POWER, FUNC_POWER);
            regularFuncMap.put(Tokens.T_SQRT, FUNC_SQRT);
            regularFuncMap.put(Tokens.T_FLOOR, FUNC_FLOOR);
            regularFuncMap.put(Tokens.T_CEILING, FUNC_CEILING);
            regularFuncMap.put(Tokens.T_CEIL, FUNC_CEILING);
            regularFuncMap.put(Tokens.T_WIDTH_BUCKET, FUNC_WIDTH_BUCKET);
            regularFuncMap.put(Tokens.T_SUBSTRING, FUNC_SUBSTRING_CHAR);
            /*
            regularFuncMap.put(Token.T_SUBSTRING_REG_EXPR,
                               FUNC_SUBSTRING_REG_EXPR);
            */
            regularFuncMap.put(Tokens.T_SUBSTRING_REGEX, FUNC_SUBSTRING_REGEX);
            regularFuncMap.put(Tokens.T_LOWER, FUNC_FOLD_LOWER);
            regularFuncMap.put(Tokens.T_UPPER, FUNC_FOLD_UPPER);
            /*
            regularFuncMap.put(Token.T_TRANSCODING, FUNC_TRANSCODING);
            regularFuncMap.put(Token.T_TRANSLITERATION, FUNC_TRANSLITERATION);
            regularFuncMap.put(Token.T_TRASLATION,
                               FUNC_REGEX_TRANSLITERATION);
            */
            regularFuncMap.put(Tokens.T_TRIM, FUNC_TRIM_CHAR);
            regularFuncMap.put(Tokens.T_OVERLAY, FUNC_OVERLAY_CHAR);
            /*
            regularFuncMap.put(Token.T_NORMALIZE, FUNC_CHAR_NORMALIZE);
            */
            regularFuncMap.put(Tokens.T_TRIM, FUNC_TRIM_BINARY);

            valueFuncMap.put(Tokens.T_CURRENT_DATE, FUNC_CURRENT_DATE);
            valueFuncMap.put(Tokens.T_CURRENT_TIME, FUNC_CURRENT_TIME);
            valueFuncMap.put(Tokens.T_CURRENT_TIMESTAMP, FUNC_CURRENT_TIMESTAMP);
            valueFuncMap.put(Tokens.T_LOCALTIME, FUNC_LOCALTIME);
            valueFuncMap.put(Tokens.T_LOCALTIMESTAMP, FUNC_LOCALTIMESTAMP);
            valueFuncMap.put(Tokens.T_CURRENT_CATALOG, FUNC_CURRENT_CATALOG);
            /*
            valueFuncMap.put(Token.T_CURRENT_DEFAULT_TRANSFORM_GROUP,
                    FUNC_CURRENT_DEFAULT_TRANSFORM_GROUP);
            */
            valueFuncMap.put(Tokens.T_CURRENT_PATH, FUNC_CURRENT_PATH);
            valueFuncMap.put(Tokens.T_CURRENT_ROLE, FUNC_CURRENT_ROLE);
            valueFuncMap.put(Tokens.T_CURRENT_SCHEMA, FUNC_CURRENT_SCHEMA);
            /*
            valueFuncMap.put(Token.T_CURRENT_TRANSFORM_GROUP_FOR_TYPE,
                    FUNC_CURRENT_TRANSFORM_GROUP_FOR_TYPE);
            */
            valueFuncMap.put(Tokens.T_CURRENT_USER, FUNC_CURRENT_USER);
            valueFuncMap.put(Tokens.T_SESSION_USER, FUNC_SESSION_USER);
            valueFuncMap.put(Tokens.T_SYSTEM_USER, FUNC_SYSTEM_USER);
            valueFuncMap.put(Tokens.T_USER, FUNC_USER);
            valueFuncMap.put(Tokens.T_VALUE, FUNC_VALUE);
        }

        //
        public int funcType;
        public String name;
        public short[] parseList;
        public short[] parseListAlt;
        public bool _isValueFunction;

        public static FunctionSQL newSQLFunction(String token,
                ParserDML.CompileContext context)
        {

            int id = regularFuncMap.get(token, -1);

            if (id == -1)
            {
                id = valueFuncMap.get(token, -1);
            }

            if (id == -1)
            {
                return null;
            }

            FunctionSQL function = new FunctionSQL(id);

            if (id == FUNC_VALUE)
            {
                if (context.currentDomain == null)
                {
                    return null;
                }

                function.dataType = context.currentDomain;
            }

            return function;
        }

        public static bool isFunction(String token)
        {
            return isRegularFunction(token) || isValueFunction(token);
        }

        public static bool isRegularFunction(String token)
        {
            return regularFuncMap.containsKey(token);
        }

        public static bool isValueFunction(String token)
        {
            return valueFuncMap.containsKey(token);
        }

        protected FunctionSQL()
            : base(OpTypes.SQL_FUNCTION)
        {

            nodes = Expression.emptyExpressionArray;
        }

        public FunctionSQL(int id)
            : this()
        {

            this.funcType = id;

            switch (id)
            {

                case FUNC_POSITION_CHAR:
                case FUNC_POSITION_BINARY:
                    name = Tokens.T_POSITION;
                    parseList = new short[] {
                    Tokens.OPENBRACKET, Tokens.QUESTION, Tokens.IN,
                    Tokens.QUESTION, Tokens.X_OPTION, 5, Tokens.USING,
                    Tokens.X_KEYSET, 2, Tokens.CHARACTERS, Tokens.OCTETS,
                    Tokens.CLOSEBRACKET
                };
                    break;

                case FUNC_OCCURENCES_REGEX:
                case FUNC_POSITION_REGEX:
                    break;

                case FUNC_EXTRACT:
                    name = Tokens.T_EXTRACT;
                    parseList = new short[] {
                    Tokens.OPENBRACKET, Tokens.X_KEYSET, 16, Tokens.YEAR,
                    Tokens.MONTH, Tokens.DAY, Tokens.HOUR, Tokens.MINUTE,
                    Tokens.SECOND, Tokens.DAY_OF_WEEK, Tokens.WEEK_OF_YEAR,
                    Tokens.QUARTER, Tokens.DAY_OF_YEAR, Tokens.DAY_OF_MONTH,
                    Tokens.DAY_NAME, Tokens.MONTH_NAME,
                    Tokens.SECONDS_MIDNIGHT, Tokens.TIMEZONE_HOUR,
                    Tokens.TIMEZONE_MINUTE, Tokens.FROM, Tokens.QUESTION,
                    Tokens.CLOSEBRACKET
                };
                    break;

                case FUNC_CHAR_LENGTH:
                    name = Tokens.T_CHAR_LENGTH;
                    parseList = new short[] {
                    Tokens.OPENBRACKET, Tokens.QUESTION, Tokens.X_OPTION, 5,
                    Tokens.USING, Tokens.X_KEYSET, 2, Tokens.CHARACTERS,
                    Tokens.OCTETS, Tokens.CLOSEBRACKET
                };
                    break;

                case FUNC_BIT_LENGTH:
                    name = Tokens.T_BIT_LENGTH;
                    parseList = singleParamList;
                    break;

                case FUNC_OCTET_LENGTH:
                    name = Tokens.T_OCTET_LENGTH;
                    parseList = singleParamList;
                    break;

                case FUNC_CARDINALITY:
                    parseList = singleParamList;
                    break;

                case FUNC_ABS:
                    name = Tokens.T_ABS;
                    parseList = singleParamList;
                    break;

                case FUNC_MOD:
                    name = Tokens.T_MOD;
                    parseList = singleParamList;
                    break;

                case FUNC_LN:
                    name = Tokens.T_LN;
                    parseList = singleParamList;
                    break;

                case FUNC_EXP:
                    name = Tokens.T_EXP;
                    parseList = singleParamList;
                    break;

                case FUNC_POWER:
                    name = Tokens.T_POWER;
                    parseList = doubleParamList;
                    break;

                case FUNC_SQRT:
                    name = Tokens.T_SQRT;
                    parseList = singleParamList;
                    break;

                case FUNC_FLOOR:
                    name = Tokens.T_FLOOR;
                    parseList = singleParamList;
                    break;

                case FUNC_CEILING:
                    name = Tokens.T_CEILING;
                    parseList = singleParamList;
                    break;

                case FUNC_WIDTH_BUCKET:
                    name = Tokens.T_WIDTH_BUCKET;
                    parseList = quadParamList;
                    break;

                case FUNC_SUBSTRING_CHAR:
                case FUNC_SUBSTRING_BINARY:
                    name = Tokens.T_SUBSTRING;
                    parseList = new short[] {
                    Tokens.OPENBRACKET, Tokens.QUESTION, Tokens.FROM,
                    Tokens.QUESTION, Tokens.X_OPTION, 2, Tokens.FOR,
                    Tokens.QUESTION, Tokens.X_OPTION, 5, Tokens.USING,
                    Tokens.X_KEYSET, 2, Tokens.CHARACTERS, Tokens.OCTETS,
                    Tokens.CLOSEBRACKET
                };
                    parseListAlt = new short[] {
                    Tokens.OPENBRACKET, Tokens.QUESTION, Tokens.COMMA,
                    Tokens.QUESTION, Tokens.COMMA, Tokens.QUESTION,
                    Tokens.CLOSEBRACKET
                };
                    break;

                /*
                case FUNCTION_SUBSTRING_REG_EXPR :
                    break;
                case FUNCTION_SUBSTRING_REGEX :
                    break;
                */
                case FUNC_FOLD_LOWER:
                    name = Tokens.T_LOWER;
                    parseList = singleParamList;
                    break;

                case FUNC_FOLD_UPPER:
                    name = Tokens.T_UPPER;
                    parseList = singleParamList;
                    break;

                /*
                case FUNCTION_TRANSCODING :
                    break;
                case FUNCTION_TRANSLITERATION :
                    break;
                case FUNCTION_REGEX_TRANSLITERATION :
                    break;
                 */
                case FUNC_TRIM_CHAR:
                case FUNC_TRIM_BINARY:
                    name = Tokens.T_TRIM;
                    parseList = new short[] {
                    Tokens.OPENBRACKET, Tokens.X_OPTION, 11,    //
                    Tokens.X_OPTION, 5,                         //
                    Tokens.X_KEYSET, 3, Tokens.LEADING, Tokens.TRAILING,
                    Tokens.BOTH,                                //
                    Tokens.X_OPTION, 1, Tokens.QUESTION,        //
                    Tokens.FROM, Tokens.QUESTION, Tokens.CLOSEBRACKET
                };
                    break;

                /*
                case FUNCTION_CHAR_NORMALIZE :
                    break;
                */
                case FUNC_OVERLAY_CHAR:
                case FUNC_OVERLAY_BINARY:
                    name = Tokens.T_OVERLAY;
                    parseList = new short[] {
                    Tokens.OPENBRACKET, Tokens.QUESTION, Tokens.PLACING,
                    Tokens.QUESTION, Tokens.FROM, Tokens.QUESTION,
                    Tokens.X_OPTION, 2, Tokens.FOR, Tokens.QUESTION,
                    Tokens.X_OPTION, 2, Tokens.USING, Tokens.CHARACTERS,
                    Tokens.CLOSEBRACKET
                };
                    break;

                case FUNC_CURRENT_CATALOG:
                    name = Tokens.T_CURRENT_CATALOG;
                    parseList = noParamList;
                    _isValueFunction = true;
                    break;

                /*
                case FUNC_CURRENT_DEFAULT_TRANSFORM_GROUP :
                    break;
                case FUNC_CURRENT_PATH :
                    break;
                */
                case FUNC_CURRENT_ROLE:
                    name = Tokens.T_CURRENT_ROLE;
                    parseList = noParamList;
                    _isValueFunction = true;
                    break;

                case FUNC_CURRENT_SCHEMA:
                    name = Tokens.T_CURRENT_SCHEMA;
                    parseList = noParamList;
                    _isValueFunction = true;
                    break;

                /*
                case FUNC_CURRENT_TRANSFORM_GROUP_FOR_TYPE :
                    break;
                */
                case FUNC_CURRENT_USER:
                    name = Tokens.T_CURRENT_USER;
                    parseList = noParamList;
                    _isValueFunction = true;
                    break;

                case FUNC_SESSION_USER:
                    name = Tokens.T_SESSION_USER;
                    parseList = noParamList;
                    _isValueFunction = true;
                    break;

                case FUNC_SYSTEM_USER:
                    name = Tokens.T_SYSTEM_USER;
                    parseList = noParamList;
                    _isValueFunction = true;
                    break;

                case FUNC_USER:
                    name = Tokens.T_USER;
                    parseList = optionalNoParamList;
                    _isValueFunction = true;
                    break;

                case FUNC_VALUE:
                    name = Tokens.T_VALUE;
                    parseList = noParamList;
                    _isValueFunction = false;
                    break;

                case FUNC_CURRENT_DATE:
                    name = Tokens.T_CURRENT_DATE;
                    parseList = noParamList;
                    _isValueFunction = true;
                    break;

                case FUNC_CURRENT_TIME:
                    name = Tokens.T_CURRENT_TIME;
                    parseList = optionalIntegerParamList;
                    _isValueFunction = true;
                    break;

                case FUNC_CURRENT_TIMESTAMP:
                    name = Tokens.T_CURRENT_TIMESTAMP;
                    parseList = optionalIntegerParamList;
                    _isValueFunction = true;
                    break;

                case FUNC_LOCALTIME:
                    name = Tokens.T_LOCALTIME;
                    parseList = optionalIntegerParamList;
                    _isValueFunction = true;
                    break;

                case FUNC_LOCALTIMESTAMP:
                    name = Tokens.T_LOCALTIMESTAMP;
                    parseList = optionalIntegerParamList;
                    _isValueFunction = true;
                    break;

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "FunctionSQL");
            }
        }

        public virtual void setArguments(Expression[] newNodes)
        {
            this.nodes = newNodes;
        }

        public virtual Expression getFunctionExpression()
        {
            return this;
        }

        /**
         * Evaluates and returns this Function in the context of the session.<p>
         */
        public override Object getValue(Session session)
        {

            Object[] data = new Object[nodes.Length];

            for (int i = 0; i < nodes.Length; i++)
            {
                Expression e = nodes[i];

                if (e != null)
                {
                    data[i] = e.getValue(session, e.dataType);
                }
            }

            return getValue(session, data);
        }

        public virtual Object getValue(Session session, Object[] data)
        {

            switch (funcType)
            {

                case FUNC_POSITION_CHAR:
                    {
                        if (data[0] == null || data[1] == null)
                        {
                            return null;
                        }

                        long result =
                            ((CharacterType)nodes[1].dataType).position(
                                session, data[1], data[0], nodes[0].dataType, 0) + 1;

                        if (nodes[2] != null
                                && ((int)nodes[2].valueData)
                                   == Tokens.OCTETS)
                        {
                            result *= 2;
                        }

                        return (result);
                    }
                case FUNC_POSITION_BINARY:
                    {
                        if (data[0] == null || data[1] == null)
                        {
                            return null;
                        }

                        long result =
                            ((BinaryType)nodes[1].dataType).position(
                                session, (BlobData)data[1], (BlobData)data[0],
                                nodes[0].dataType, 0) + 1;

                        if (nodes[2] != null
                                && ((int)nodes[2].valueData)
                                   == Tokens.OCTETS)
                        {
                            result *= 2;
                        }

                        return (result);
                    }
                /*
                case FUNC_OCCURENCES_REGEX :
                case FUNC_POSITION_REGEX :
                */
                case FUNC_EXTRACT:
                    {
                        if (data[1] == null)
                        {
                            return null;
                        }

                        int part = ((int)nodes[0].valueData);

                        part = DTIType.getFieldNameTypeForToken(part);

                        switch (part)
                        {

                            case Types.SQL_INTERVAL_SECOND:
                                {
                                    return ((DTIType)nodes[1].dataType).getSecondPart(
                                        data[1]);
                                }
                            case DTIType.MONTH_NAME:
                            case DTIType.DAY_NAME:
                                {
                                    return ((DateTimeType)nodes[1].dataType)
                                        .getPartString(session, data[1], part);
                                }
                            default:
                                {
                                    int value =
                                        ((DTIType)nodes[1].dataType).getPart(session,
                                            data[1], part);

                                    return (value);
                                }
                        }
                    }
                case FUNC_CHAR_LENGTH:
                    {
                        if (data[0] == null)
                        {
                            return null;
                        }

                        long result = ((CharacterType)nodes[0].dataType).size(session,
                            data[0]);

                        return (result);
                    }
                case FUNC_BIT_LENGTH:
                    {
                        if (data[0] == null)
                        {
                            return null;
                        }

                        long result;

                        if (nodes[0].dataType.isBinaryType())
                        {
                            result = ((BlobData)data[0]).bitLength(session);
                        }
                        else
                        {
                            result =
                                16 * ((CharacterType)nodes[0].dataType).size(session,
                                    data[0]);
                        }

                        return (result);
                    }
                case FUNC_OCTET_LENGTH:
                    {
                        if (data[0] == null)
                        {
                            return null;
                        }

                        long result;

                        if (nodes[0].dataType.isBinaryType())
                        {
                            result = ((BlobData)data[0]).length(session);
                        }
                        else
                        {
                            result =
                                2 * ((CharacterType)nodes[0].dataType).size(session,
                                    data[0]);
                        }

                        return (result);
                    }
                /*
                case FUNC_CARDINALITY :
                */
                case FUNC_ABS:
                    {
                        if (data[0] == null)
                        {
                            return null;
                        }

                        return dataType.absolute(data[0]);
                    }
                case FUNC_MOD:
                    {
                        if (data[0] == null || data[1] == null)
                        {
                            return null;
                        }

                        // non-integral arguments are accepted with conversion

                        /** @todo - check if widening has an effect */
                        Object value =
                            ((NumberType)nodes[0].dataType).divide(nodes[0],
                                nodes[1]);

                        value = ((NumberType)nodes[0].dataType).subtract(nodes[0],
                                value, nodes[1].dataType);

                        // result type is the same as argList[1]
                        return ((NumberType)dataType).convertToTypeLimits(session,
                                value);
                    }
                case FUNC_LN:
                    {
                        if (data[0] == null)
                        {
                            return null;
                        }

                        double d = Convert.ToDouble(data[0]);

                        if (d <= 0)
                        {
                            throw Error.error(ErrorCode.X_2201E);
                        }

                        d = Math.Log(d);

                        return d;
                    }
                case FUNC_EXP:
                    {
                        if (data[0] == null)
                        {
                            return null;
                        }

                        double val = Math.Exp((double)data[0]);

                        return val;
                    }
                case FUNC_POWER:
                    {
                        if (data[0] == null || data[1] == null)
                        {
                            return null;
                        }

                        double _base = ((double)data[0]);
                        double exponent = ((double)data[1]);
                        double val;

                        if (exponent < 0)
                        {
                            throw Error.error(ErrorCode.X_2201F);
                        }

                        if (_base == 0)
                        {
                            if (exponent < 0)
                            {
                                throw Error.error(ErrorCode.X_2201F);
                            }
                            else if (exponent == 0)
                            {
                                val = 1;
                            }
                            else
                            {
                                val = 0;
                            }
                        }
                        else
                        {
                            val = Math.Pow(_base, exponent);
                        }

                        return val;
                    }
                case FUNC_SQRT:
                    {
                        if (data[0] == null)
                        {
                            return null;
                        }

                        double val = Math.Sqrt(Convert.ToDouble(data[0]));
                        return val;
                    }
                case FUNC_FLOOR:
                    {
                        if (data[0] == null)
                        {
                            return null;
                        }

                        return ((NumberType)dataType).floor(data[0]);
                    }
                case FUNC_CEILING:
                    {
                        if (data[0] == null)
                        {
                            return null;
                        }

                        return ((NumberType)dataType).ceiling(data[0]);
                    }
                case FUNC_WIDTH_BUCKET:
                    {
                        return null;
                    }
                case FUNC_SUBSTRING_CHAR:
                    {
                        if (data[0] == null || data[1] == null)
                        {
                            return null;
                        }

                        Object value;

                        value = SqlType.SQL_BIGINT.convertToType(session, data[1],
                                                              nodes[1].dataType);

                        long offset = ((long)value) - 1;
                        long length = 0;

                        if (nodes[2] != null)
                        {
                            if (data[2] == null)
                            {
                                return null;
                            }

                            value = SqlType.SQL_BIGINT.convertToType(session, data[2],
                                                                  nodes[2].dataType);
                            length = ((long)value);
                        }

                        if (nodes.Length > 3 && nodes[3] != null
                                && ((int)nodes[2].valueData)
                                   == Tokens.OCTETS)
                        {

                            // not clear what the rules on USING OCTECTS are with UTF
                        }

                        return ((CharacterType)dataType).substring(session, data[0],
                                offset, length, nodes[2] != null, false);
                    }
                /*
                case FUNCTION_SUBSTRING_REG_EXPR :
                    break;
                case FUNCTION_SUBSTRING_REGEX :
                    break;
                */
                case FUNC_FOLD_LOWER:
                    if (data[0] == null)
                    {
                        return null;
                    }

                    return ((CharacterType)dataType).lower(session, data[0]);

                case FUNC_FOLD_UPPER:
                    if (data[0] == null)
                    {
                        return null;
                    }

                    return ((CharacterType)dataType).upper(session, data[0]);

                /*
                case FUNCTION_TRANSCODING :
                    break;
                case FUNCTION_TRANSLITERATION :
                    break;
                case FUNCTION_REGEX_TRANSLITERATION :
                    break;
                 */
                case FUNC_TRIM_CHAR:
                    {
                        if (data[1] == null || data[2] == null)
                        {
                            return null;
                        }

                        bool leading = false;
                        bool trailing = false;

                        switch ((int)nodes[0].valueData)
                        {

                            case Tokens.BOTH:
                                leading = trailing = true;
                                break;

                            case Tokens.LEADING:
                                leading = true;
                                break;

                            case Tokens.TRAILING:
                                trailing = true;
                                break;

                            default:
                                throw Error.runtimeError(ErrorCode.U_S0500,
                                                         "FunctionSQL");
                        }

                        String str = (String)data[1];

                        if (str.Length != 1)
                        {
                            throw Error.error(ErrorCode.X_22027);
                        }

                        int character = str[0];

                        return ((CharacterType)dataType).trim(session, data[2],
                                                               character, leading,
                                                               trailing);
                    }
                case FUNC_OVERLAY_CHAR:
                    {
                        if (data[0] == null || data[1] == null || data[2] == null)
                        {
                            return null;
                        }

                        Object value;

                        value = SqlType.SQL_BIGINT.convertToType(session, data[2],
                                                              nodes[2].dataType);

                        long offset = ((long)value) - 1;
                        long length = 0;

                        if (nodes[3] != null)
                        {
                            if (data[3] == null)
                            {
                                return null;
                            }

                            value = SqlType.SQL_BIGINT.convertToType(session, data[3],
                                                                  nodes[3].dataType);
                            length = ((long)value);
                        }

                        return ((CharacterType)dataType).overlay(null, data[0],
                                data[1], offset, length, nodes[3] != null);
                    }
                /*
                case FUNCTION_CHAR_NORMALIZE :
                    break;
                */
                case FUNC_SUBSTRING_BINARY:
                    {
                        if (data[0] == null || data[1] == null)
                        {
                            return null;
                        }

                        Object value;

                        value = SqlType.SQL_BIGINT.convertToType(session, data[1],
                                                              nodes[1].dataType);

                        long offset = ((long)value) - 1;
                        long length = 0;

                        if (nodes[2] != null)
                        {
                            if (data[2] == null)
                            {
                                return null;
                            }

                            value = SqlType.SQL_BIGINT.convertToType(session, data[2],
                                                                  nodes[2].dataType);
                            length = ((int)value);
                        }

                        return ((BinaryType)dataType).substring(session,
                                (BlobData)data[0], offset, length, nodes[2] != null);
                    }
                case FUNC_TRIM_BINARY:
                    {
                        if (data[1] == null || data[2] == null)
                        {
                            return null;
                        }

                        bool leading = false;
                        bool trailing = false;
                        int spec = ((int)nodes[0].valueData);

                        switch (spec)
                        {

                            case Tokens.BOTH:
                                leading = trailing = true;
                                break;

                            case Tokens.LEADING:
                                leading = true;
                                break;

                            case Tokens.TRAILING:
                                trailing = true;
                                break;

                            default:
                                throw Error.runtimeError(ErrorCode.U_S0500,
                                                         "FunctionSQL");
                        }

                        BlobData str = (BlobData)data[1];

                        if (str.length(session) != 1)
                        {
                            throw Error.error(ErrorCode.X_22027);
                        }

                        byte[] bytes = str.getBytes();

                        return ((BinaryType)dataType).trim(session,
                                                            (BlobData)data[3],
                                                            bytes[0], leading,
                                                            trailing);
                    }
                case FUNC_OVERLAY_BINARY:
                    {
                        if (data[0] == null || data[1] == null || data[2] == null)
                        {
                            return null;
                        }

                        Object value;

                        value = SqlType.SQL_BIGINT.convertToType(session, data[2],
                                                              nodes[2].dataType);

                        long offset = ((long)value) - 1;
                        long length = 0;

                        if (nodes[3] != null)
                        {
                            if (data[3] == null)
                            {
                                return null;
                            }

                            value = SqlType.SQL_BIGINT.convertToType(session, data[3],
                                                                  nodes[3].dataType);
                            length = ((long)value);
                        }

                        return ((BinaryType)dataType).overlay(session,
                                                               (BlobData)data[0],
                                                               (BlobData)data[1],
                                                               offset, length,
                                                               nodes[3] != null);
                    }
                case FUNC_CURRENT_CATALOG:
                    return session.database.getCatalogName().name;

                /*
                case FUNC_CURRENT_DEFAULT_TRANSFORM_GROUP :
                case FUNC_CURRENT_PATH :
                */
                case FUNC_CURRENT_ROLE:
                    return session.getRole() == null ? null
                                                     : session.getRole()
                                                         .getNameString();

                case FUNC_CURRENT_SCHEMA:
                    return session.getCurrentSchemaHsqlName().name;

                /*
                case FUNC_CURRENT_TRANSFORM_GROUP_FOR_TYPE :
                */
                case FUNC_CURRENT_USER:
                    return session.getUser().getNameString();

                case FUNC_SESSION_USER:
                    return session.getUser().getNameString();

                case FUNC_SYSTEM_USER:
                    return session.getUser().getNameString();

                case FUNC_USER:
                    return session.getUser().getNameString();

                case FUNC_VALUE:
                    return session.sessionData.currentValue;

                case FUNC_CURRENT_DATE:
                    return session.getCurrentDate();

                case FUNC_CURRENT_TIME:
                    return dataType.convertToTypeLimits(
                        session, session.getCurrentTime(true));

                case FUNC_CURRENT_TIMESTAMP:
                    return dataType.convertToTypeLimits(
                        session, session.getCurrentTimestamp(true));

                case FUNC_LOCALTIME:
                    return dataType.convertToTypeLimits(
                        session, session.getCurrentTime(false));

                case FUNC_LOCALTIMESTAMP:
                    return dataType.convertToTypeLimits(
                        session, session.getCurrentTimestamp(false));

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "FunctionSQL");
            }
        }

        public override void resolveTypes(Session session, Expression parent)
        {

            for (int i = 0; i < nodes.Length; i++)
            {
                if (nodes[i] != null)
                {
                    nodes[i].resolveTypes(session, this);
                }
            }

            switch (funcType)
            {

                case FUNC_POSITION_CHAR:
                case FUNC_POSITION_BINARY:
                    {
                        if (nodes[0].dataType == null)
                        {
                            if (nodes[1].dataType == null)
                            {
                                throw Error.error(ErrorCode.X_42567);
                            }

                            if (nodes[1].dataType.typeCode == Types.SQL_CLOB
                                    || nodes[1].dataType.isBinaryType())
                            {
                                nodes[0].dataType = nodes[1].dataType;
                            }
                            else
                            {
                                nodes[0].dataType = SqlType.SQL_VARCHAR;
                            }
                        }

                        if (nodes[1].dataType == null)
                        {
                            if (nodes[0].dataType.typeCode == Types.SQL_CLOB
                                    || nodes[0].dataType.isBinaryType())
                            {
                                nodes[1].dataType = nodes[0].dataType;
                            }
                            else
                            {
                                nodes[1].dataType = SqlType.SQL_VARCHAR;
                            }
                        }

                        if (nodes[0].dataType.isCharacterType()
                                && nodes[1].dataType.isCharacterType())
                        {
                            funcType = FUNC_POSITION_CHAR;
                        }
                        else if (nodes[0].dataType.isBinaryType()
                                 && nodes[1].dataType.isBinaryType())
                        {
                            if (nodes[0].dataType.isBitType()
                                    || nodes[1].dataType.isBitType())
                            {
                                throw Error.error(ErrorCode.X_42565);
                            }

                            funcType = FUNC_POSITION_BINARY;
                        }
                        else
                        {
                            throw Error.error(ErrorCode.X_42565);
                        }

                        dataType = SqlType.SQL_BIGINT;

                        break;
                    }
                /*
                case FUNC_OCCURENCES_REGEX :
                case FUNC_POSITION_REGEX :
                */
                case FUNC_EXTRACT:
                    {
                        if (nodes[1].dataType == null)
                        {
                            throw Error.error(ErrorCode.X_42567);
                        }

                        if (!nodes[1].dataType.isDateTimeType()
                                && !nodes[1].dataType.isIntervalType())
                        {
                            throw Error.error(ErrorCode.X_42565);
                        }

                        int part = ((int)nodes[0].valueData);
                        DTIType type = (DTIType)nodes[1].dataType;

                        part = DTIType.getFieldNameTypeForToken(part);
                        dataType = type.getExtractType(part);

                        break;
                    }
                case FUNC_BIT_LENGTH:
                    {
                        if (nodes[0].dataType == null)
                        {
                            nodes[0].dataType = SqlType.SQL_BIT_VARYING_MAX_LENGTH;
                        }

                        if (!nodes[0].dataType.isCharacterType()
                                && !nodes[0].dataType.isBinaryType())
                        {
                            throw Error.error(ErrorCode.X_42565);
                        }

                        dataType = SqlType.SQL_BIGINT;

                        break;
                    }
                case FUNC_CHAR_LENGTH:
                    if (!nodes[0].dataType.isCharacterType())
                    {
                        throw Error.error(ErrorCode.X_42565);
                    }
                    goto case FUNC_OCTET_LENGTH;
                // fall through
                case FUNC_OCTET_LENGTH:
                    {
                        if (nodes[0].dataType == null)
                        {
                            nodes[0].dataType = SqlType.SQL_VARCHAR;
                        }

                        if (!nodes[0].dataType.isCharacterType()
                                && !nodes[0].dataType.isBinaryType())
                        {
                            throw Error.error(ErrorCode.X_42565);
                        }

                        dataType = SqlType.SQL_BIGINT;

                        break;
                    }
                case FUNC_CARDINALITY:
                    {
                        dataType = SqlType.SQL_BIGINT;

                        break;
                    }
                case FUNC_MOD:
                    {
                        if (nodes[0].dataType == null)
                        {
                            nodes[1].dataType = nodes[0].dataType;
                        }

                        if (nodes[1].dataType == null)
                        {
                            nodes[0].dataType = nodes[1].dataType;
                        }

                        if (nodes[0].dataType == null)
                        {
                            throw Error.error(ErrorCode.X_42567);
                        }

                        if (!nodes[0].dataType.isNumberType()
                                || !nodes[1].dataType.isNumberType())
                        {
                            throw Error.error(ErrorCode.X_42565);
                        }

                        nodes[0].dataType =
                            ((NumberType)nodes[0].dataType).getIntegralType();
                        nodes[1].dataType =
                            ((NumberType)nodes[1].dataType).getIntegralType();
                        dataType = nodes[1].dataType;

                        break;
                    }
                case FUNC_POWER:
                    {
                        if (nodes[0].dataType == null)
                        {
                            nodes[1].dataType = nodes[0].dataType;
                        }

                        if (nodes[1].dataType == null)
                        {
                            nodes[0].dataType = nodes[1].dataType;
                        }

                        if (nodes[0].dataType == null)
                        {
                            throw Error.error(ErrorCode.X_42567);
                        }

                        if (!nodes[0].dataType.isNumberType()
                                || !nodes[1].dataType.isNumberType())
                        {
                            throw Error.error(ErrorCode.X_42565);
                        }

                        nodes[0].dataType = SqlType.SQL_DOUBLE;
                        nodes[1].dataType = SqlType.SQL_DOUBLE;
                        dataType = SqlType.SQL_DOUBLE;

                        break;
                    }
                case FUNC_LN:
                case FUNC_EXP:
                case FUNC_SQRT:
                    {
                        if (nodes[0].dataType == null)
                        {
                            nodes[0].dataType = SqlType.SQL_DOUBLE;
                        }

                        if (!nodes[0].dataType.isNumberType())
                        {
                            throw Error.error(ErrorCode.X_42565);
                        }

                        nodes[0].dataType = SqlType.SQL_DOUBLE;
                        dataType = SqlType.SQL_DOUBLE;

                        break;
                    }
                case FUNC_ABS:
                    if (nodes[0].dataType != null
                            && nodes[0].dataType.isIntervalType())
                    {
                        dataType = nodes[0].dataType;

                        break;
                    }
                    goto case FUNC_FLOOR;
                // fall through
                case FUNC_FLOOR:
                case FUNC_CEILING:
                    {
                        if (nodes[0].dataType == null)
                        {
                            nodes[0].dataType = SqlType.SQL_DOUBLE;
                        }

                        if (!nodes[0].dataType.isNumberType())
                        {
                            throw Error.error(ErrorCode.X_42565);
                        }

                        dataType = nodes[0].dataType;

                        break;
                    }
                case FUNC_WIDTH_BUCKET:
                    {
                        if (nodes[0].dataType == null || nodes[1].dataType == null
                                || nodes[2].dataType == null
                                || nodes[3].dataType == null)
                        {
                            throw Error.error(ErrorCode.X_42567);
                        }

                        if (!nodes[0].dataType.isNumberType()
                                || !nodes[1].dataType.isNumberType()
                                || !nodes[2].dataType.isNumberType()
                                || !nodes[3].dataType.isIntegralType())
                        {
                            throw Error.error(ErrorCode.X_42565);
                        }

                        dataType = nodes[3].dataType;

                        break;
                    }
                case FUNC_SUBSTRING_CHAR:
                case FUNC_SUBSTRING_BINARY:
                    {
                        if (nodes[0].dataType == null)
                        {

                            // in 20.6 parameter not allowed as type cannot be determined as binary or char
                            throw Error.error(ErrorCode.X_42567);
                        }

                        if (nodes[1].dataType == null)
                        {
                            nodes[1].dataType = SqlType.SQL_NUMERIC;
                        }

                        if (!nodes[1].dataType.isNumberType())
                        {
                            throw Error.error(ErrorCode.X_42565);
                        }

                        if (nodes[2] != null)
                        {
                            if (nodes[2].dataType == null)
                            {
                                nodes[2].dataType = SqlType.SQL_NUMERIC;
                            }

                            if (!nodes[2].dataType.isNumberType())
                            {
                                throw Error.error(ErrorCode.X_42565);
                            }

                            nodes[2].dataType =
                                ((NumberType)nodes[2].dataType).getIntegralType();
                        }

                        dataType = nodes[0].dataType;

                        if (dataType.isCharacterType())
                        {
                            funcType = FUNC_SUBSTRING_CHAR;

                            if (dataType.typeCode == Types.SQL_CHAR)
                            {
                                dataType =
                                    CharacterType.getCharacterType(Types.SQL_VARCHAR,
                                                                   dataType.precision);
                            }
                        }
                        else if (dataType.isBinaryType())
                        {
                            funcType = FUNC_SUBSTRING_BINARY;
                        }
                        else
                        {
                            throw Error.error(ErrorCode.X_42565);
                        }

                        if (nodes.Length > 3 && nodes[3] != null)
                        {

                            // always bool constant if defined
                        }

                        break;
                    }
                /*
                case FUNCTION_SUBSTRING_REG_EXPR :
                    break;
                case FUNCTION_SUBSTRING_REGEX :
                    break;
                */
                case FUNC_FOLD_LOWER:
                case FUNC_FOLD_UPPER:
                    if (nodes[0].dataType == null)
                    {
                        throw Error.error(ErrorCode.X_42567);
                    }

                    dataType = nodes[0].dataType;

                    if (!dataType.isCharacterType())
                    {
                        throw Error.error(ErrorCode.X_42565);
                    }
                    break;

                /*
                case FUNCTION_TRANSCODING :
                    break;
                case FUNCTION_TRANSLITERATION :
                    break;
                case FUNCTION_REGEX_TRANSLITERATION :
                    break;
                 */
                case FUNC_TRIM_CHAR:
                case FUNC_TRIM_BINARY:
                    if (nodes[0] == null)
                    {
                        nodes[0] =
                            new ExpressionValue((Tokens.BOTH),
                                                SqlType.SQL_INTEGER);
                    }

                    if (nodes[2].dataType == null)
                    {
                        throw Error.error(ErrorCode.X_42567);
                    }

                    dataType = nodes[2].dataType;

                    if (dataType.isCharacterType())
                    {
                        funcType = FUNC_TRIM_CHAR;

                        if (dataType.typeCode == Types.SQL_CHAR)
                        {
                            dataType =
                                CharacterType.getCharacterType(Types.SQL_VARCHAR,
                                                               dataType.precision);
                        }

                        if (nodes[1] == null)
                        {
                            nodes[1] = new ExpressionValue(" ", SqlType.SQL_CHAR);
                        }
                    }
                    else if (dataType.isBinaryType())
                    {
                        funcType = FUNC_TRIM_BINARY;

                        if (nodes[1] == null)
                        {
                            nodes[1] = new ExpressionValue(
                                new BinaryData(new byte[] { 0 }, false),
                                SqlType.SQL_BINARY);
                        }
                    }
                    else
                    {
                        throw Error.error(ErrorCode.X_42565);
                    }
                    break;

                case FUNC_OVERLAY_CHAR:
                case FUNC_OVERLAY_BINARY:
                    {
                        if (nodes[0].dataType == null)
                        {
                            if (nodes[1].dataType == null)
                            {
                                throw Error.error(ErrorCode.X_42567);
                            }

                            if (nodes[1].dataType.typeCode == Types.SQL_CLOB
                                    || nodes[1].dataType.isBinaryType())
                            {
                                nodes[0].dataType = nodes[1].dataType;
                            }
                            else
                            {
                                nodes[0].dataType = SqlType.SQL_VARCHAR;
                            }
                        }

                        if (nodes[1].dataType == null)
                        {
                            if (nodes[0].dataType.typeCode == Types.SQL_CLOB
                                    || nodes[0].dataType.isBinaryType())
                            {
                                nodes[1].dataType = nodes[0].dataType;
                            }
                            else
                            {
                                nodes[1].dataType = SqlType.SQL_VARCHAR;
                            }
                        }

                        if (nodes[0].dataType.isCharacterType()
                                && nodes[1].dataType.isCharacterType())
                        {
                            funcType = FUNC_OVERLAY_CHAR;

                            if (nodes[0].dataType.typeCode == Types.SQL_CLOB
                                    || nodes[1].dataType.typeCode == Types.SQL_CLOB)
                            {
                                dataType = CharacterType.getCharacterType(
                                    Types.SQL_CLOB,
                                    nodes[0].dataType.precision
                                    + nodes[1].dataType.precision);
                            }
                            else
                            {
                                dataType = CharacterType.getCharacterType(
                                    Types.SQL_VARCHAR,
                                    nodes[0].dataType.precision
                                    + nodes[1].dataType.precision);
                            }
                        }
                        else if (nodes[0].dataType.isBinaryType()
                                 && nodes[1].dataType.isBinaryType())
                        {
                            funcType = FUNC_OVERLAY_BINARY;

                            if (nodes[0].dataType.typeCode == Types.SQL_BLOB
                                    || nodes[1].dataType.typeCode == Types.SQL_BLOB)
                            {
                                dataType = BinaryType.getBinaryType(
                                    Types.SQL_BLOB,
                                    nodes[0].dataType.precision
                                    + nodes[1].dataType.precision);
                            }
                            else
                            {
                                dataType = BinaryType.getBinaryType(
                                    Types.SQL_VARBINARY,
                                    nodes[0].dataType.precision
                                    + nodes[1].dataType.precision);
                            }
                        }
                        else
                        {
                            throw Error.error(ErrorCode.X_42565);
                        }

                        if (nodes[2].dataType == null)
                        {
                            nodes[2].dataType = SqlType.SQL_NUMERIC;
                        }

                        if (!nodes[2].dataType.isNumberType())
                        {
                            throw Error.error(ErrorCode.X_42565);
                        }

                        nodes[2].dataType =
                            ((NumberType)nodes[2].dataType).getIntegralType();

                        if (nodes[3] != null)
                        {
                            if (nodes[3].dataType == null)
                            {
                                nodes[3].dataType = SqlType.SQL_NUMERIC;
                            }

                            if (!nodes[3].dataType.isNumberType())
                            {
                                throw Error.error(ErrorCode.X_42565);
                            }

                            nodes[3].dataType =
                                ((NumberType)nodes[3].dataType).getIntegralType();
                        }

                        break;
                    }
                /*
                case FUNCTION_CHAR_NORMALIZE :
                    break;
                */
                case FUNC_CURRENT_CATALOG:
                case FUNC_CURRENT_DEFAULT_TRANSFORM_GROUP:
                case FUNC_CURRENT_PATH:
                case FUNC_CURRENT_ROLE:
                case FUNC_CURRENT_SCHEMA:
                case FUNC_CURRENT_TRANSFORM_GROUP_FOR_TYPE:
                case FUNC_CURRENT_USER:
                case FUNC_SESSION_USER:
                case FUNC_SYSTEM_USER:
                case FUNC_USER:
                    dataType = SqlInvariants.SQL_IDENTIFIER;
                    break;

                case FUNC_VALUE:
                    break;

                case FUNC_CURRENT_DATE:
                    dataType = CharacterType.SQL_DATE;
                    break;

                case FUNC_CURRENT_TIME:
                    {
                        int precision = DateTimeType.defaultTimeFractionPrecision;

                        if (nodes[0] != null)
                        {
                            precision = ((int)nodes[0].valueData);
                        }

                        dataType =
                            DateTimeType.getDateTimeType(Types.SQL_TIME_WITH_TIME_ZONE,
                                                         precision);

                        break;
                    }
                case FUNC_CURRENT_TIMESTAMP:
                    {
                        int precision = DateTimeType.defaultTimestampFractionPrecision;

                        if (nodes.Length > 0 && nodes[0] != null)
                        {
                            precision = ((int)nodes[0].valueData);
                        }

                        dataType = DateTimeType.getDateTimeType(
                            Types.SQL_TIMESTAMP_WITH_TIME_ZONE, precision);

                        break;
                    }
                case FUNC_LOCALTIME:
                    {
                        int precision = DateTimeType.defaultTimeFractionPrecision;

                        if (nodes.Length > 0 && nodes[0] != null)
                        {
                            precision = ((int)nodes[0].valueData);
                        }

                        dataType = DateTimeType.getDateTimeType(Types.SQL_TIME,
                                precision);

                        break;
                    }
                case FUNC_LOCALTIMESTAMP:
                    {
                        int precision = DateTimeType.defaultTimestampFractionPrecision;

                        if (nodes.Length > 0 && nodes[0] != null)
                        {
                            precision = ((int)nodes[0].valueData);
                        }

                        dataType = DateTimeType.getDateTimeType(Types.SQL_TIMESTAMP,
                                precision);

                        break;
                    }
                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "FunctionSQL");
            }
        }

        public override String getSQL()
        {

            StringBuilder sb = new StringBuilder();

            switch (funcType)
            {

                case FUNC_POSITION_CHAR:
                case FUNC_POSITION_BINARY:
                    {
                        sb.Append(Tokens.T_POSITION).Append('(')                 //
                            .Append(nodes[0].getSQL()).Append(' ')               //
                            .Append(Tokens.T_IN).Append(' ')                     //
                            .Append(nodes[1].getSQL());

                        if (nodes[2] != null
                                && true.Equals(nodes[2].valueData))
                        {
                            sb.Append(' ').Append(Tokens.T_USING).Append(' ').Append(
                                Tokens.T_OCTETS);
                        }

                        sb.Append(')');

                        break;
                    }
                case FUNC_OCCURENCES_REGEX:
                    break;

                case FUNC_POSITION_REGEX:
                    break;

                case FUNC_EXTRACT:
                    {
                        int type = ((int)nodes[0].valueData);

                        type = DTIType.getFieldNameTypeForToken(type);

                        String token = DTIType.getFieldNameTokenForType(type);

                        sb.Append(Tokens.T_EXTRACT).Append('(').Append(token)    //
                            .Append(' ').Append(Tokens.T_FROM).Append(' ')       //
                            .Append(nodes[1].getSQL()).Append(')');

                        break;
                    }
                case FUNC_CHAR_LENGTH:
                    {
                        sb.Append(Tokens.T_CHAR_LENGTH).Append('(')              //
                            .Append(nodes[0].getSQL()).Append(')');

                        break;
                    }
                case FUNC_BIT_LENGTH:
                    {
                        sb.Append(Tokens.T_BIT_LENGTH).Append('(')               //
                            .Append(nodes[0].getSQL()).Append(')');

                        break;
                    }
                case FUNC_OCTET_LENGTH:
                    {
                        sb.Append(Tokens.T_OCTET_LENGTH).Append('(')             //
                            .Append(nodes[0].getSQL()).Append(')');

                        break;
                    }
                /*
                case FUNC_CARDINALITY :{
                    buf.Append(Token.T_CARDINALITY).Append('(').Append(
                        argList[0].getSQL()).Append(')');

                    break;
                }
                */
                case FUNC_ABS:
                    {
                        sb.Append(Tokens.T_ABS).Append('(')                      //
                            .Append(nodes[0].getSQL()).Append(')');

                        break;
                    }
                case FUNC_MOD:
                    {
                        sb.Append(Tokens.T_MOD).Append('(')                      //
                            .Append(nodes[0].getSQL()).Append(',')               //
                            .Append(nodes[1].getSQL()).Append(')');

                        break;
                    }
                case FUNC_LN:
                    {
                        sb.Append(Tokens.T_LN).Append('(')                       //
                            .Append(nodes[0].getSQL()).Append(')');

                        break;
                    }
                case FUNC_EXP:
                    {
                        sb.Append(Tokens.T_EXP).Append('(')                      //
                            .Append(nodes[0].getSQL()).Append(')');

                        break;
                    }
                case FUNC_POWER:
                    {
                        sb.Append(Tokens.T_POWER).Append('(')                    //
                            .Append(nodes[0].getSQL()).Append(',')               //
                            .Append(nodes[1].getSQL()).Append(')');

                        break;
                    }
                case FUNC_SQRT:
                    {
                        sb.Append(Tokens.T_SQRT).Append('(')                     //
                            .Append(nodes[0].getSQL()).Append(')');

                        break;
                    }
                case FUNC_FLOOR:
                    {
                        sb.Append(Tokens.T_FLOOR).Append('(')                    //
                            .Append(nodes[0].getSQL()).Append(')');

                        break;
                    }
                case FUNC_CEILING:
                    {
                        sb.Append(Tokens.T_CEILING).Append('(').                 //
                            Append(nodes[0].getSQL()).Append(')');

                        break;
                    }
                case FUNC_WIDTH_BUCKET:
                    {
                        sb.Append(Tokens.T_WIDTH_BUCKET).Append('(')             //
                            .Append(nodes[0].getSQL()).Append(',')               //
                            .Append(nodes[1].getSQL()).Append(',')               //
                            .Append(nodes[2].getSQL()).Append(',')               //
                            .Append(nodes[3].getSQL()).Append(')');

                        break;
                    }
                case FUNC_SUBSTRING_CHAR:
                case FUNC_SUBSTRING_BINARY:
                    sb.Append(Tokens.T_SUBSTRING).Append('(')                //
                        .Append(nodes[0].getSQL()).Append(' ')               //
                        .Append(Tokens.T_FROM).Append(' ')                   //
                        .Append(nodes[1].getSQL());

                    if (nodes[2] != null)
                    {
                        sb.Append(' ').Append(Tokens.T_FOR).Append(' ')      //
                            .Append(nodes[2].getSQL());
                    }

                    if (nodes.Length > 3 && nodes[3] != null)
                    {
                        if (true.Equals(nodes[3].valueData))
                        {
                            sb.Append(' ').Append(Tokens.T_USING).Append(
                                ' ').Append(Tokens.T_OCTETS);
                        }
                    }

                    sb.Append(')');
                    break;

                /*
                case FUNCTION_SUBSTRING_REGEX :
                    break;
                */
                case FUNC_FOLD_LOWER:
                    sb.Append(Tokens.T_LOWER).Append('(').Append(
                        nodes[0].getSQL()).Append(')');
                    break;

                case FUNC_FOLD_UPPER:
                    sb.Append(Tokens.T_UPPER).Append('(').Append(
                        nodes[0].getSQL()).Append(')');
                    break;

                /*
                case FUNCTION_TRANSCODING :
                    break;
                case FUNCTION_TRANSLITERATION :
                    break;
                case FUNCTION_REGEX_TRANSLITERATION :
                    break;
                 */
                case FUNC_OVERLAY_CHAR:
                case FUNC_OVERLAY_BINARY:
                    sb.Append(Tokens.T_OVERLAY).Append('(')                  //
                        .Append(nodes[0].getSQL()).Append(' ')               //
                        .Append(Tokens.T_PLACING).Append(' ')                //
                        .Append(nodes[1].getSQL()).Append(' ')               //
                        .Append(Tokens.T_FROM).Append(' ')                   //
                        .Append(nodes[2].getSQL());

                    if (nodes[3] != null)
                    {
                        sb.Append(' ').Append(Tokens.T_FOR).Append(' ').Append(
                            nodes[3].getSQL());
                    }

                    if (nodes[4] != null)
                    {
                        if (true.Equals(nodes[4].valueData))
                        {
                            sb.Append(' ').Append(Tokens.T_USING).Append(
                                ' ').Append(Tokens.T_OCTETS);
                        }
                    }

                    sb.Append(')');
                    break;

                /*
                case FUNCTION_NORMALIZE :
                    break;
                */
                case FUNC_TRIM_CHAR:
                case FUNC_TRIM_BINARY:
                    String spec = null;

                    switch (((int)nodes[0].valueData))
                    {

                        case Tokens.BOTH:
                            spec = Tokens.T_BOTH;
                            break;

                        case Tokens.LEADING:
                            spec = Tokens.T_LEADING;
                            break;

                        case Tokens.TRAILING:
                            spec = Tokens.T_TRAILING;
                            break;
                    }

                    sb.Append(Tokens.T_TRIM).Append('(')                     //
                        .Append(spec).Append(' ')                            //
                        .Append(nodes[1].getSQL()).Append(' ')               //
                        .Append(Tokens.T_FROM).Append(' ')                   //
                        .Append(nodes[2].getSQL()).Append(')');
                    break;

                case FUNC_CURRENT_CATALOG:
                case FUNC_CURRENT_DEFAULT_TRANSFORM_GROUP:
                case FUNC_CURRENT_PATH:
                case FUNC_CURRENT_ROLE:
                case FUNC_CURRENT_SCHEMA:
                case FUNC_CURRENT_TRANSFORM_GROUP_FOR_TYPE:
                case FUNC_CURRENT_USER:
                case FUNC_SESSION_USER:
                case FUNC_SYSTEM_USER:
                case FUNC_USER:
                case FUNC_CURRENT_DATE:
                case FUNC_VALUE:
                    return name;

                case FUNC_LOCALTIME:
                case FUNC_CURRENT_TIME:
                    {
                        int precision = DateTimeType.defaultTimeFractionPrecision;

                        if (nodes.Length > 0 && nodes[0] != null)
                        {
                            precision = ((int)nodes[0].valueData);
                        }

                        if (precision == DateTimeType.defaultTimeFractionPrecision)
                        {
                            return name;
                        }

                        sb.Append(name).Append(Tokens.T_OPENBRACKET).Append(precision);
                        sb.Append(Tokens.T_CLOSEBRACKET);

                        return sb.ToString();
                    }
                case FUNC_LOCALTIMESTAMP:
                case FUNC_CURRENT_TIMESTAMP:
                    {
                        int precision = DateTimeType.defaultTimestampFractionPrecision;

                        if (nodes.Length > 0 && nodes[0] != null)
                        {
                            precision = ((int)nodes[0].valueData);
                        }

                        if (precision
                                == DateTimeType.defaultTimestampFractionPrecision)
                        {
                            return name;
                        }

                        sb.Append(name).Append(Tokens.T_OPENBRACKET).Append(precision);
                        sb.Append(Tokens.T_CLOSEBRACKET);

                        return sb.ToString();
                    }
                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "FunctionSQL");
            }

            return sb.ToString();
        }

        public bool equals(Object other)
        {

            if (other is FunctionSQL
                    && funcType == ((FunctionSQL)other).funcType)
            {
                return base.Equals(other);
            }

            return false;
        }

        public int hashCode()
        {
            return opType + funcType;
        }

        /**
         * Returns a String representation of this object. <p>
         */
        public override String describe(Session session, int blanks)
        {

            StringBuilder sb = new StringBuilder();

            sb.Append('\n');

            for (int i = 0; i < blanks; i++)
            {
                sb.Append(' ');
            }

            sb.Append("FUNCTION ").Append("=[\n");
            sb.Append(name).Append("(");

            for (int i = 0; i < nodes.Length; i++)
            {
                sb.Append("[").Append(nodes[i].describe(session)).Append("]");
            }

            sb.Append(") returns ").Append(dataType.getNameString());
            sb.Append("]\n");

            return sb.ToString();
        }

        public bool isValueFunction()
        {
            return _isValueFunction;
        }
    }
}
